package com.log4jviewer.filters;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.log4jviewer.domain.LogEvent;
import com.log4jviewer.domain.LogField;

/**
 * Filter for input logs.
 * 
 * @author <a href="mailto:rd.ryly@gmail.com">Ruslan Diachenko</a>
 */
public class LogFilterEngine {

    private FilterModel filter;

    private List<Pattern> filterRegExp;

    public void setFilter(final FilterModel filter) {
        this.filter = filter;

        if (filter != null) {
            compileFilterPattern(this.filter);
        }
    }

    private void compileFilterPattern(final FilterModel filter) {
        filterRegExp = new ArrayList<Pattern>();

        for (int i = 0; i < filter.getItemsCount(); i++) {
            String regExp = filter.getItem(i).getPattern();
            Pattern pattern = Pattern.compile(regExp, Pattern.CASE_INSENSITIVE);
            filterRegExp.add(pattern);
        }
    }

    public boolean isFiltered(final LogEvent log) {
        boolean passedFilter = true;
        int itemCount = 0;

        while ((filter != null) && (itemCount < filter.getItemsCount())) {
            String logicOperand = filter.getItem(itemCount).getLogicOperand().getValue();

            if (FilterConstants.OR_LOGIC_OPERAND.equals(logicOperand)) {
                FilterItemModel filterItemModel = filter.getItem(itemCount);

                if (isMatched(getLogFieldInfo(log, filterItemModel), filterItemModel)) {
                    passedFilter = true;
                }
                itemCount++;
            }

            while ((itemCount < filter.getItemsCount())
                    && (FilterConstants.AND_LOGIC_OPERAND.equals(logicOperand))) {
                FilterItemModel filterItemModel = filter.getItem(itemCount);

                if (!isMatched(getLogFieldInfo(log, filterItemModel), filterItemModel)) {
                    passedFilter = false;

                    while ((itemCount < filter.getItemsCount()) && (FilterConstants.AND_LOGIC_OPERAND
                            .equals(logicOperand))) {
                        itemCount++;

                        if (itemCount < filter.getItemsCount()) {
                            logicOperand = filter.getItem(itemCount).getLogicOperand().getValue();
                        }
                    }
                    break;
                }
                itemCount++;

                if (itemCount < filter.getItemsCount()) {
                    logicOperand = filter.getItem(itemCount).getLogicOperand().getValue();
                }
            }

            if (passedFilter) {
                break;
            }
        }
        return passedFilter;
    }

    private String getLogFieldInfo(final LogEvent log, final FilterItemModel filterItemModel) {
        String logFieldInfo = "";

        switch (filterItemModel.getFieldType()) {
        case LEVEL:
            logFieldInfo = LogField.LEVEL.getFieldInfo(log);
            break;
        case MESSAGE:
            logFieldInfo = LogField.MESSAGE.getFieldInfo(log);
            break;
        case CATEGORY:
            logFieldInfo = LogField.CATEGORY.getFieldInfo(log);
            break;
        case LINE:
            logFieldInfo = LogField.LINE.getFieldInfo(log);
            break;
        case DATE:
            logFieldInfo = LogField.DATE.getFieldInfo(log);
            break;
        case NDC:
            logFieldInfo = LogField.NDC.getFieldInfo(log);
            break;
        case THROWABLE:
            logFieldInfo = LogField.THROWABLE.getFieldInfo(log);
            break;
        default:
            throw new IllegalArgumentException("Log field type: "
                    + filterItemModel.getFieldType().getValue()
                    + " is undefined. Only one of these log field types is available:"
                    + " level|message|category|line|date|ndc|throwable");
        }

        return logFieldInfo;
    }

    /**
     * Checks if logs pass matching pattern
     * 
     * @param columnType
     *            - Column type which contains one of the log4j fields (Level|Message|Category|Line|Date|NDC|Throwable)
     * @param itemIndex
     *            - Item index in Item list
     * @return If logs are include or exclude with specified pattern return log, if logs didn't pass matching return
     *         false
     * @throws RESyntaxException
     */
    private boolean isMatched(final String logFieldInfo, final FilterItemModel filterItemModel) {
        String fieldType = filterItemModel.getFieldType().getValue();
        boolean isExpressionMatchLogField = false;

        if (FilterConstants.LEVEL.equals(fieldType) || FilterConstants.CATEGORY.equals(fieldType)
                || FilterConstants.LINE.equals(fieldType)) {
            if (logFieldInfo.equals(filterItemModel.getPattern())) {
                isExpressionMatchLogField = true;
            }
        } else {
            int regExpIndex = filter.getItemIndex(filterItemModel);
            Pattern pattern = filterRegExp.get(regExpIndex);
            Matcher matcher = pattern.matcher(logFieldInfo);
            isExpressionMatchLogField = matcher.find();
        }

        return isExpressionMatchLogField == filterItemModel.isInclude();
    }
}
